/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include <iostream>
#include <sstream>

#include "Utils/Fonts/FontManager.hpp"

using namespace std;

class AutoFace
{
    FT_Face& f;
    bool     own;
public:
    AutoFace(FT_Face& face)
        : f(face), own(true)
    {}

    ~AutoFace()
    {
        if (own) 
        {
            FT_Done_Face(f);
        }
    }
    
    FT_Face release() 
    {
        own = false;
        return f;
    }

    FT_Face get()
    {
        return f;
    }
};

FontManager::FontManager()
    : current_face(0), delete_current(false), use_kerning(true)
{
    /* initialize library */
    FT_Error error = FT_Init_FreeType( &library );
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    rotate_matrix.xx = 0;
    rotate_matrix.yx = 65535;
    rotate_matrix.xy = -65535;
    rotate_matrix.yy = 0;
}

FontManager::~FontManager()
{
    for(FONTS::iterator p = fonts.begin();
        p != fonts.end();
        ++p)
    {
        for (FONT_STYLES::iterator i = p->second.begin();
             i != p->second.end();
             ++i)
        {
            FT_Done_Face(i->second);
        }
    }

    FT_Done_FreeType( library );
}


void FontManager::addFont(const std::string& file)
{
    FT_Face face;

    FT_Error error = FT_New_Face( library, 
                                  file.c_str(),
                                  0, 
                                  &face );
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    AutoFace aface(face);

    addCreatedFace(aface.get());
    
    aface.release();
}

void FontManager::addCreatedFace(FT_Face face)
{
    FONT_STYLES::value_type fname(face->style_name, face);
    
    FONTS::iterator p = fonts.find(face->family_name);
    if (p != fonts.end())
    {
        p->second.insert(fname);
    }
    else
    {
        FONTS::value_type fnt(face->family_name, FONT_STYLES());
        
        fnt.second.insert(fname);
        fonts.insert(fnt);
    }
}

void FontManager::useFont(const std::string& file, bool add)
{
    FT_Face face;

    FT_Error error = FT_New_Face( library, 
                                  file.c_str(),
                                  0, 
                                  &face );
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    AutoFace aface(face);

    FONTS::iterator p = fonts.find(aface.get()->family_name);
    if (p != fonts.end())
    {
        FONT_STYLES::iterator i = p->second.find(aface.get()->style_name);
        if (i != p->second.end())
        {
            if (delete_current)
            {
                FT_Done_Face(current_face);
            }

            delete_current = false;
            current_face = i->second;

            return;
        }
    }

    if (add)
    {
        addCreatedFace(aface.get());
    }
    
    if (delete_current)
    {
        FT_Done_Face(current_face);
    }

    delete_current = !add;

    current_face   = aface.get();
    
    aface.release();
}

int FontManager::renderLine(DrawingArea&          area,
                            const PageOrientation orient,
                            const wchar_t*        str,
                            const TextStyle&      style)
{
    isFontAvailable(style.FontFile(), true);

    useFont(style.Font(), style.Style());

    useKerning(style.UseKerning());

    return renderLine(area,
                      orient,
                      str,
                      style.Size(),
                      style.TextAlign());
}

void FontManager::renderChar(DrawingArea&          area,
                             const PageOrientation orient,
                             const wchar_t&        str,
                             const TextStyle&      style)
{
    isFontAvailable(style.FontFile(), true);

    useFont(style.Font(), style.Style());
    
    useKerning(style.UseKerning());

    renderChar(area,
               orient,
               str,
               style.Size());
}

bool FontManager::isFontAvailable(const std::string& file, bool download)
{
    FT_Face face;

    FT_Error error = FT_New_Face( library, 
                                  file.c_str(),
                                  0, 
                                  &face );
    if (error)
    {
        std::string msg (__FUNCTION__);
        msg += ": Cannot open face for ";
        msg += file;

        throw FontManagerError(msg);
    }

    AutoFace aface(face);

    FONTS::iterator p = fonts.find(face->family_name);
    if (p != fonts.end())
    {
        return true;
    }
        
    if (!download)
    {
        return false;
    }
    addCreatedFace(aface.get());

    current_face   = aface.release();
//    aface.release();

    return true;
}

void FontManager::useFont(const std::string& font_name, 
                          const std::string& style)
{
    FONTS::iterator p = fonts.find(font_name);
    if (p == fonts.end())
    {
        ostringstream os;
        os << __FUNCTION__ 
           << ":Font " << font_name << " is not available"
           << "\nAvailable fonts are:";
        for(p = fonts.begin(); p != fonts.end(); ++p)
        {
            os << p->first << endl;
        }
        throw FontManagerError(os.str());
    }

    FONT_STYLES::iterator i;
    if (style.empty())
    {
        i = p->second.begin();
    }
    else
    {
        i = p->second.find(style);
    }

    if (i == p->second.end())
    {
        ostringstream os;
        os << __FUNCTION__ << ": Font style "
           << style << " not found, font "
           << font_name 
           << "\nAvailable styles are:";
        for(i = p->second.begin(); i != p->second.end(); ++i)
        {
            os << i->first << endl;
        }

        throw FontManagerError(os.str());
    }

    if (delete_current)
    {
        FT_Done_Face(current_face);
    }

    delete_current = false;
    current_face = i->second;
}


int FontManager::renderLine(DrawingArea&          area,
                            const PageOrientation orient,
                            const wchar_t*        str,
                            const size_t          font_size,
                            const Align           align)
{
    int num_chars = wcslen( str );

    FT_Error error = FT_Set_Pixel_Sizes( current_face,
                                         0, 
                                         font_size);
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    FT_GlyphSlot slot = current_face->glyph;

    int start = 0;

    FT_Bool kerning = use_kerning ? FT_HAS_KERNING(current_face): false; 

    FT_UInt  previous = 0;
    FT_UInt  glyph_index;

    if (orient == Landscape)
    {
        FT_Set_Transform(current_face, &rotate_matrix, 0);
        if (error)
        {
            throw FontManagerError(error, __FUNCTION__);
        }
    }
    else
    {
        FT_Set_Transform(current_face, 0, 0);
        if (error)
        {
            throw FontManagerError(error, __FUNCTION__);
        }
    }

    for (int n = 0; n < num_chars; ++n)
    {
        glyph_index = FT_Get_Char_Index( current_face, str[n] );

        if ( kerning && previous && glyph_index ) 
        {
            FT_Vector delta; 
            error = FT_Get_Kerning( current_face, 
                                    previous, 
                                    glyph_index, 
                                    FT_KERNING_DEFAULT, 
                                    &delta ); 
            if (!error)
                start += delta.x >> 6; 
        }
        error = FT_Load_Glyph( current_face, 
                               glyph_index, 
                               FT_LOAD_RENDER | FT_LOAD_MONOCHROME);
        if (error)
        {
            throw FontManagerError(error, __FUNCTION__);
        }
        
        bool in_area = drawImage(area, 
                                 slot, 
                                 start,
                                 orient);

        if (!in_area)
        {
            return n;
        }

        start = slot->advance.x >> 6;

        previous = glyph_index;
    }

    return num_chars;
}

void FontManager::renderChar(DrawingArea&          area,
                             const PageOrientation orient,
                             const wchar_t&        symbol,
                             const size_t&         font_size)
{
    FT_Error error = FT_Set_Pixel_Sizes( current_face,
                                         0, 
                                         font_size);
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }

    FT_GlyphSlot slot = current_face->glyph;

    FT_UInt glyph_index = FT_Get_Char_Index( current_face, symbol);

    error = FT_Load_Glyph( current_face, 
                           glyph_index, 
                           FT_LOAD_RENDER | FT_LOAD_MONOCHROME);
    if (error)
    {
        throw FontManagerError(error, __FUNCTION__);
    }
    
    drawImage(area, 
              slot, 
              0,
              orient);
}

bool FontManager::drawImage(DrawingArea& area, 
                            const FT_GlyphSlot    glyph, 
                            const int             start,
                            const PageOrientation orient)
{
    if (orient == Landscape)
    {
        area.rotate();
    }
    int rowSize = glyph->bitmap.pitch;

    area.pen.X += start;
    
    for(int y = 0;
        y < glyph->bitmap.rows;
        ++y)
    {
     	int y1 = area.pen.Y + y - glyph->bitmap_top;
        if (y1 <= 0) y1 = 0;

        if (y1 + 1 > (long)area.coord.end.Y)
            return false;

       	for( int x = 0;
             x < glyph->bitmap.width;
             ++x)
        {
            int x1 = x + area.pen.X + glyph->bitmap_left;

            if (x1 + 1 > (long)area.coord.end.X)
                return false;

            unsigned char* src = glyph->bitmap.buffer 
                + y * rowSize 
                + (x >> 3);

            if((*src >> (7 - x&0x7)) & 0x1)
            {
                unsigned char* des 
                    = (unsigned char*) area.buffer 
                      + y1 * area.width 
                      + (x1 >> 2);

                *des &= ~(0xC0 >> ((x1 & 0x3) << 0x1));
            }
        }
    }

    if (orient == Landscape)
    {
        area.rotate();
    }

    return true;
}
